<?php
# autoHTML: the dynamic PHP5 library for fast website prototyping
# http://code.google.com/p/autohtml/
#
# Copyright (c) 2010, Shu Zong Chen
# http://freelanceDreams.com/
# All rights reserved. 
# 
# Redistribution and use in source and binary forms, with or without 
# modification, are permitted provided that the following conditions are met: 
# 
#  * Redistributions of source code must retain the above copyright notice, 
#    this list of conditions and the following disclaimer. 
#  * Redistributions in binary form must reproduce the above copyright 
#    notice, this list of conditions and the following disclaimer in the 
#    documentation and/or other materials provided with the distribution. 
#  * Neither the name of autoHTML nor the names of its contributors 
#    may be used to endorse or promote products derived from this software 
#    without specific prior written permission. 
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
# POSSIBILITY OF SUCH DAMAGE.

namespace fd\autoserver;

require_once('response.php');
require_once('connection.php');
require_once('header.php');
require_once('site.php');
global $socket;

error_reporting(E_ALL);
set_time_limit(0);
ob_implicit_flush();

function socket_get_error($socket){
	return socket_strerror(socket_last_error($socket));
}
function has_pcntl(){
	return function_exists('pcntl_signal');
}
function exception_error_handler($errno, $errstr, $errfile, $errline ) {
    throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
}
function exception_handler($exception){
	echo 'Exception handler caught uncaught exception: ' , $exception->getMessage(), PHP_EOL;
	$a = $exception->getTrace();
	print_r($a[0]);
}
function close_sockets(){
	global $socket;
	echo PHP_EOL, 'Dying!...', PHP_EOL;
	if (is_resource($socket)){
		socket_set_block($socket);
		socket_shutdown($socket, 1);
		usleep(500);
		socket_shutdown($socket, 0);
		usleep(500);
		socket_close($socket);
	}
	echo 'Dead', PHP_EOL;
}
if (has_pcntl()){
	function signal_handler($signal) {
		close_sockets();
		exit();
	}
	declare(ticks=1);
	pcntl_signal(SIGTERM, 'fd\autoserver\signal_handler');
	pcntl_signal(SIGINT, 'fd\autoserver\signal_handler');
}

set_error_handler('\fd\autoserver\exception_error_handler');
set_exception_handler('\fd\autoserver\exception_handler');


########## We actually start out here ##############
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$site = new SITE_FS('/home/sirpengi/code/autohtml/public_html');
$server = new AUTOSERVER($site, $socket);

try{
	$server->init();
}catch (\Exception $e){
	echo $e->getMessage(), PHP_EOL;
	exit();
}

$server->listen();

class AUTOSERVER{
	protected $address = '127.0.0.1';
	protected $port = 8000;
	protected $connections = array();
	protected $queue_limit = 20;
	protected $verbose;
	protected $socket;
	function __construct($site, $socket = null){
		$this->site = $site;
		if ($socket === null){
			$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
		}
		$this->socket = $socket;
		$this->verbose = false;
	}
	function setAddress($addr){
		$this->address = $addr;
	}
	function setPort($port){
		$this->port = (int)$port;
	}
	function getSite(){
		return $this->site;
	}
	function init(){
		$r = socket_bind($this->socket, $this->address, $this->port); 
		if ($r === false){
			throw new \Exception(socket_get_error());
		}else{
			echo 'Socket bound to ', $this->address,':',$this->port, PHP_EOL;
		}
		$r = socket_listen($this->socket, $this->queue_limit);
		if ($r === false){
			throw new \Exception(socket_get_error());
		}else{
			echo 'Socket listening.', PHP_EOL;
		}
		$r = socket_set_block($this->socket);
		if ($r ===false){
			throw new \Exception(socket_get_error($this->socket));
		}
		$r = socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO,	array('sec'=>1, 'usec'=>0));
		if ($r ===false){
			throw new \Exception(socket_get_error($this->socket));
		}
	}
	function listen(){
		//used later for socket mapping hack
		$get_sockets = function($s){	return $s->getSocket();	};

		do {
			# echo date('r') , PHP_EOL, ':' , sizeof($this->connections) , ' live connections', PHP_EOL;
			# first priority: accept connections
			try{
				$new_s = false;
				$new_s = @socket_accept($this->socket);
			}catch (\Exception $e){
				
			}
			if ($new_s !== false){
				//new socket, make one now
				$a = $p = null;
				socket_getpeername($new_s, $a, $p);
				$name = "s_$p";
				$this->connections[$name] = new CONNECTION($this, $name, $new_s);
				socket_set_nonblock($this->socket);
				continue;
			}

			foreach ($this->connections as $socket){
				$socket->parse();
			}

			# read/write from all until we make a smarter dispatch
			$conn_list = array_map($get_sockets, $this->connections);
			$read = $write = $except = $conn_list;

			if (sizeof($conn_list)){
				$a = @socket_select($read, $write, $except, 0);
				if ($a){
					foreach ($read as $key=>$conn){
						$this->connections[$key]->listen();
					}
					foreach ($write as $key=>$conn){
						$this->connections[$key]->respond();
					}
					foreach ($except as $key=>$socket){
						echo 'Exception occured in: ', PHP_EOL;
						var_dump($key);
					}
				}
			}

			#close and remove all done sockets
			foreach ($this->connections as $conn){
				if ($conn->isDead()){
					$conn->close();
					unset($this->connections[$conn->getName()]);
				}
			}

			if (sizeof($this->connections)){
				socket_set_nonblock($this->socket);
			}else{
				socket_set_block($this->socket);
			}
		}while (true);
	}
}